Mestre Pythons unittest.mock-bibliotek. Dypdykk i test-doubles, mock-objekter, stubs, fakes og patch-dekoratøren for robust, isolert enhetstesting.
Python Mock-objekter: En omfattende guide til implementering av test-doubles
I moderne programvareutvikling er skriving av kode bare halve slaget. Å sikre at koden er pålitelig, robust og fungerer som forventet er den andre, like kritiske halvdelen. Det er her automatisert testing kommer inn. Spesielt enhetstesting er en grunnleggende praksis som innebærer å teste individuelle komponenter eller 'enheter' av en applikasjon isolert. Denne isolasjonen er imidlertid ofte lettere sagt enn gjort. Virkelige applikasjoner er komplekse nettverk av sammenkoblede objekter, tjenester og eksterne systemer. Hvordan kan du teste en enkelt funksjon hvis den avhenger av en database, et tredjeparts API, eller en annen kompleks del av systemet ditt?
Svaret ligger i en kraftig teknikk: bruken av test-doubles. Og i Python-økosystemet er hovedverktøyet for å lage dem det allsidige og uunnværlige unittest.mock-biblioteket. Denne guiden vil ta deg med på et dypt dykk inn i verden av mocks og test-doubles i Python. Vi vil utforske 'hvorfor' bak dem, demystifisere de forskjellige typene, og gi praktiske eksempler fra den virkelige verden ved bruk av unittest.mock for å hjelpe deg med å skrive renere, raskere og mer effektive tester.
Hva er test-doubles og hvorfor trenger vi dem?
Forestill deg at du bygger en funksjon som henter en brukers profil fra firmaets database og deretter formaterer den. Funksjonssignatur kan se slik ut: get_formatted_user_profile(user_id, db_connection).
For å teste denne funksjonen, står du overfor flere utfordringer:
- Avhengighet av et live system: Testen din vil trenge en kjørende database. Dette gjør tester trege, komplekse å sette opp, og avhengige av et eksternt systems tilstand og tilgjengelighet.
- Uforutsigbarhet: Dataene i databasen kan endres, noe som får testen din til å feile selv om formateringslogikken din er korrekt. Dette gjør tester 'flaky' eller ikke-deterministiske.
- Vanskeligheter med å teste grensetilfeller: Hvordan ville du testet hva som skjer hvis databaseforbindelsen svikter, eller hvis den returnerer en bruker som mangler noen data? Å simulere disse spesifikke scenariene med en ekte database kan være utrolig vanskelig.
En test-double er en generell betegnelse på ethvert objekt som erstatter et ekte objekt under en test. Ved å erstatte den ekte db_connection med en test-double, kan vi bryte avhengigheten av den faktiske databasen og ta full kontroll over testmiljøet.
Bruk av test-doubles gir flere viktige fordeler:
- Isolasjon: De lar deg teste kodenheten din (f.eks. formateringslogikken) i full isolasjon fra dens avhengigheter (f.eks. databasen). Hvis testen feiler, vet du at problemet ligger i enheten som testes, ikke et annet sted.
- Hastighet: Å erstatte trege operasjoner som nettverksforespørsler eller databasekall med en test-double i minnet gjør at testpakken din kjører dramatisk raskere. Raske tester kjøres oftere, noe som fører til en tettere tilbakemeldingssløyfe for utviklere.
- Determinisme: Du kan konfigurere test-doublen til å returnere forutsigbare data hver gang testen kjøres. Dette eliminerer 'flaky' tester og sikrer at en feilende test indikerer et reelt problem.
- Mulighet til å teste grensetilfeller: Du kan enkelt konfigurere en double til å simulere feiltilstander, som å kaste en
ConnectionErroreller returnere tomme data, noe som lar deg verifisere at koden din håndterer disse situasjonene på en grasiøs måte.
Taksomien av test-doubles: Mer enn bare "Mocks"
Selv om utviklere ofte bruker begrepet "mock" generisk for å referere til enhver test-double, er det nyttig å forstå den mer presise terminologien som ble introdusert av Gerard Meszaros i hans bok "xUnit Test Patterns". Å kjenne til disse forskjellene hjelper deg med å tenke klarere om hva du prøver å oppnå i testen din.
1. Dummy
Et Dummy-objekt er den enkleste test-doublen. Det sendes rundt for å fylle en parameterliste, men blir aldri faktisk brukt. Metodene blir typisk ikke kalt. Du bruker en dummy når du trenger å gi et argument til en metode, men du bryr deg ikke om at argumentets oppførsel i konteksten av den spesifikke testen.
Eksempel: Hvis en funksjon krever et 'logger'-objekt, men testen din ikke er opptatt av hva som logges, kan du gi et dummy-objekt.
2. Fake
Et Fake-objekt har en fungerende implementasjon, men det er en mye enklere versjon av produksjonsobjektet. Det bruker ikke eksterne ressurser og erstatter en lettvektsimplementasjon med en tungvektsimplementasjon. Det klassiske eksemplet er en database i minnet som erstatter en ekte databaseforbindelse. Den fungerer faktisk – du kan legge til data i den og lese data fra den – men det er bare en enkel ordbok eller liste under panseret.
3. Stub
En Stub gir forhåndsprogrammerte, "kapslede" svar på metodekall som gjøres under en test. Den brukes når du trenger at koden din skal motta spesifikke data fra en avhengighet. For eksempel kan du stubbe en metode som api_client.get_user(user_id=123) til å alltid returnere en spesifikk bruker-ordbok, uten å faktisk foreta et API-kall.
4. Spy
En Spy er en stub som også registrerer informasjon om hvordan den ble kalt. For eksempel kan den registrere antall ganger en metode ble kalt eller argumentene som ble sendt til den. Dette lar deg "spionere" på interaksjonen mellom koden din og dens avhengighet, og deretter gjøre påstander om den interaksjonen etterpå.
5. Mock
En Mock er den mest "bevisste" typen test-double. Det er et objekt som er forhåndsprogrammert med forventninger om hvilke metoder som vil bli kalt, med hvilke argumenter, og i hvilken rekkefølge. En test som bruker et mock-objekt vil typisk feile ikke bare hvis koden som testes produserer feil resultat, men også hvis den ikke interagerer med mocken på den presist forventede måten. Mocks er flotte for atferdsverifisering – å sikre at en spesifikk sekvens av handlinger skjedde.
Pythons unittest.mock-bibliotek tilbyr en enkelt, kraftig klasse som kan fungere som en Stub, Spy, eller Mock, avhengig av hvordan du bruker den.
Introduksjon til Pythons kraftpakke: `unittest.mock`-biblioteket
En del av Pythons standardbibliotek siden versjon 3.3, unittest.mock er den kanoniske løsningen for å lage test-doubles. Dens fleksibilitet og kraft gjør den til et uunnværlig verktøy for enhver seriøs Python-utvikler. Hvis du bruker en eldre versjon av Python, kan du installere den bakoverkompatible biblioteket via pip: pip install mock.
Bibliotekets kjerne dreier seg om to nøkkelklasser: Mock og dens mer kapable søsken, MagicMock. Disse objektene er designet for å være utrolig fleksible, og skaper attributter og metoder "on the fly" etter hvert som du får tilgang til dem.
Dypdykk: `Mock` og `MagicMock`-klassene
`Mock`-objektet
Et `Mock`-objekt er et kamelion. Du kan lage en, og den vil umiddelbart svare på enhver attributt-tilgang eller metodekall, og returnere et annet Mock-objekt som standard. Dette lar deg enkelt koble sammen kall under oppsett.
# I en testfil...
from unittest.mock import Mock
# Opprett et mock-objekt
mock_api = Mock()
# Tilgang til en attributt oppretter den og returnerer en annen mock
print(mock_api.users)
# Utdata: <Mock name='mock.users' id='...'>
# Kaller en metode returnerer også en mock som standard
print(mock_api.users.get(id=1))
# Utdata: <Mock name='mock.users.get()' id='...'>
Denne standardatferden er ikke spesielt nyttig for testing. Den virkelige kraften kommer fra å konfigurere mocken til å oppføre seg som objektet den erstatter.
Konfigurering av returverdier og sideeffekter
Du kan fortelle en mock-metode hva den skal returnere ved hjelp av return_value-attributtet. Slik lager du en Stub.
from unittest.mock import Mock
# Opprett en mock for en datatjeneste
mock_service = Mock()
# Konfigurer returverdien for et metodekall
mock_service.get_data.return_value = {'id': 1, 'name': 'Test Data'}
# Nå når vi kaller den, får vi vår konfigurerte verdi
result = mock_service.get_data()
print(result)
# Utdata: {'id': 1, 'name': 'Test Data'}
For å simulere feil, kan du bruke side_effect-attributtet. Dette er perfekt for å teste kodens feilhåndtering.
from unittest.mock import Mock
mock_service = Mock()
# Konfigurer metoden til å kaste et unntak
mock_service.get_data.side_effect = ConnectionError("Kunne ikke koble til tjenesten")
# Kaller metoden vil nå kaste unntaket
try:
mock_service.get_data()
except ConnectionError as e:
print(e)
# Utdata: Kunne ikke koble til tjenesten
Påstandmetoder for verifisering
Mock-objekter fungerer også som Spies og Mocks ved å registrere hvordan de blir brukt. Du kan deretter bruke en samling innebygde påstandmetoder for å verifisere disse interaksjonene.
mock_object.method.assert_called(): Bekrefter at metoden ble kalt minst én gang.mock_object.method.assert_called_once(): Bekrefter at metoden ble kalt nøyaktig én gang.mock_object.method.assert_called_with(*args, **kwargs): Bekrefter at metoden sist ble kalt med de angitte argumentene.mock_object.method.assert_any_call(*args, **kwargs): Bekrefter at metoden ble kalt med disse argumentene på et hvilket som helst tidspunkt.mock_object.method.assert_not_called(): Bekrefter at metoden aldri ble kalt.mock_object.call_count: En heltallsegenskap som forteller deg hvor mange ganger metoden ble kalt.
from unittest.mock import Mock
mock_notifier = Mock()
# Tenk at dette er funksjonen din under test
def process_and_notify(data, notifier):
if data.get('critical'):
notifier.send_alert(message="Kritisk hendelse inntraff!")
# Test case 1: Kritisk data
process_and_notify({'critical': True}, mock_notifier)
mock_notifier.send_alert.assert_called_once_with(message="Kritisk hendelse inntraff!")
# Tilbakestill mocken for neste test
mock_notifier.reset_mock()
# Test case 2: Ikke-kritisk data
process_and_notify({'critical': False}, mock_notifier)
mock_notifier.send_alert.assert_not_called()
`MagicMock`-objektet
En `MagicMock` er en underklasse av `Mock` med en viktig forskjell: den har standardimplementasjoner for de fleste av Pythons "magiske" eller "dunder" metoder (f.eks. __len__, __str__, __iter__). Hvis du prøver å bruke en vanlig `Mock` i en kontekst som krever en av disse metodene, får du en feil.
from unittest.mock import Mock, MagicMock
# Bruker en vanlig Mock
mock_list = Mock()
try:
len(mock_list)
except TypeError as e:
print(e) # Utdata: 'Mock' object has no len()
# Bruker en MagicMock
magic_mock_list = MagicMock()
print(len(magic_mock_list)) # Utdata: 0 (som standard)
# Vi kan også konfigurere returverdien for den magiske metoden
magic_mock_list.__len__.return_value = 100
print(len(magic_mock_list)) # Utdata: 100
Tommelfingerregel: Start med `MagicMock`. Den er generelt tryggere og dekker flere bruksområder, som å mocke objekter som brukes i for-løkker (krever __iter__) eller with-setninger (krever __enter__ og __exit__).
Praktisk implementering: `patch`-dekoratøren og kontekstbehandleren
Å lage en mock er én ting, men hvordan får du koden din til å bruke den i stedet for det ekte objektet? Det er her `patch` kommer inn. `patch` er et kraftig verktøy i `unittest.mock` som midlertidig erstatter et målobjekt med en mock for varigheten av en test.
`@patch` som dekoratør
Den vanligste måten å bruke `patch` på er som en dekoratør på testmetoden din. Du angir strengstien til objektet du vil erstatte.
La oss si at vi har en funksjon som henter data fra et web API ved bruk av det populære `requests`-biblioteket:
# i fil: my_app/data_fetcher.py
import requests
def get_user_data(user_id):
response = requests.get(f"https://api.example.com/users/{user_id}")
response.raise_for_status() # Kaster et unntak for dårlige statuskoder
return response.json()
Vi ønsker å teste denne funksjonen uten å foreta et ekte nettverkskall. Vi kan patche `requests.get`:
# i fil: tests/test_data_fetcher.py
import unittest
from unittest.mock import patch, Mock
from my_app.data_fetcher import get_user_data
class TestDataFetcher(unittest.TestCase):
@patch('my_app.data_fetcher.requests.get')
def test_get_user_data_success(self, mock_get):
"""Test vellykket datahenting."""
# Konfigurer mocken til å simulere et vellykket API-svar
mock_response = Mock()
mock_response.json.return_value = {'id': 1, 'name': 'John Doe'}
mock_response.raise_for_status.return_value = None # Gjør ingenting ved suksess
mock_get.return_value = mock_response
# Kall funksjonen vår
user_data = get_user_data(1)
# Bekreft at funksjonen vår foretok det korrekte API-kallet
mock_get.assert_called_once_with('https://api.example.com/users/1')
# Bekreft at funksjonen vår returnerte de forventede dataene
self.assertEqual(user_data, {'id': 1, 'name': 'John Doe'})
Legg merke til hvordan `patch` oppretter en `MagicMock` og sender den inn i testmetoden vår som `mock_get`-argumentet. Innenfor testen omdirigeres ethvert kall til `requests.get` inne i `my_app.data_fetcher` til mock-objektet vårt.
`patch` som kontekstbehandler
Noen ganger trenger du bare å patche noe for en liten del av en test. Bruk av `patch` som en kontekstbehandler med en `with`-setning er perfekt for dette.
# i fil: tests/test_data_fetcher.py
import unittest
from unittest.mock import patch, Mock
from my_app.data_fetcher import get_user_data
class TestDataFetcher(unittest.TestCase):
def test_get_user_data_with_context_manager(self):
"""Test bruk av patch som kontekstbehandler."""
with patch('my_app.data_fetcher.requests.get') as mock_get:
# Konfigurer mocken inne i 'with'-blokken
mock_response = Mock()
mock_response.json.return_value = {'id': 2, 'name': 'Jane Doe'}
mock_get.return_value = mock_response
user_data = get_user_data(2)
mock_get.assert_called_once_with('https://api.example.com/users/2')
self.assertEqual(user_data, {'id': 2, 'name': 'Jane Doe'})
# Utenfor 'with'-blokken er requests.get tilbake til sin opprinnelige tilstand
Et kritisk konsept: Hvor skal du patche?
Dette er den enkelt mest vanlige kilden til forvirring når du bruker `patch`. Regelen er: Du må patche objektet der det slås opp, ikke der det er definert.
La oss illustrere med et eksempel. Anta at vi har to filer:
# i fil: services.py
class Database:
def connect(self):
# ... kompleks tilkoblingslogikk ...
return "REAL_CONNECTION"
# i fil: main_app.py
from services import Database
def start_app():
db = Database()
connection = db.connect()
print(f"Fikk tilkobling: {connection}")
return connection
Nå ønsker vi å teste `start_app` i `main_app.py` uten å opprette et ekte `Database`-objekt. En vanlig feil er å prøve å patche `services.Database`.
# i fil: test_main_app.py
import unittest
from unittest.mock import patch
from main_app import start_app
class TestApp(unittest.TestCase):
# DENNE ER FEIL MÅTE Å PATCHE PÅ!
@patch('services.Database')
def test_start_app_incorrectly(self, mock_db):
start_app()
# Denne testen vil fortsatt bruke den EKTE Database-klassen!
# DENNE ER KORREKT MÅTE Å PATCHE PÅ!
@patch('main_app.Database')
def test_start_app_correctly(self, mock_db_class):
# Vi patchet 'Database' i 'main_app'-navnerommet
# Konfigurer mock-instansen som vil bli opprettet
mock_instance = mock_db_class.return_value
mock_instance.connect.return_value = "MOCKED_CONNECTION"
connection = start_app()
# Bekreft at mocken vår ble brukt
mock_db_class.assert_called_once() # Ble klassen instansiert?
mock_instance.connect.assert_called_once() # Ble connect-metoden kalt?
self.assertEqual(connection, "MOCKED_CONNECTION")
Hvorfor feiler den første testen? Fordi `main_app.py` kjører `from services import Database`. Dette importerer `Database`-klassen inn i `main_app`-modulens navnerom. Når `start_app` kjører, ser den etter `Database` innenfor sin egen modul (`main_app`). Patche av `services.Database` endrer den i `services`-modulen, men `main_app` har allerede sin egen referanse til den originale klassen. Den korrekte tilnærmingen er å patche `main_app.Database`, som er navnet koden som testes faktisk bruker.
Avanserte Mocking-teknikker
`spec` og `autospec`: Gjør Mocks tryggere
En standard `MagicMock` har en potensiell ulempe: den lar deg kalle enhver metode med vilkårlige argumenter, selv om metoden ikke eksisterer på det ekte objektet. Dette kan føre til tester som passerer, men som skjuler reelle problemer, som skrivefeil i metodenavn eller endringer i en ekte objekts API.
# Ekte klasse
class Notifier:
def send_message(self, text):
# ... sender melding ...
pass
# En test med en skrivefeil
from unittest.mock import MagicMock
mock_notifier = MagicMock()
# Oops, en skrivefeil! Den ekte metoden er send_message
mock_notifier.send_mesage("hei") # Ingen feil kastes!
mock_notifier.send_mesage.assert_called_with("hei") # Denne påstanden passerer!
# Testen vår er grønn, men produksjonskoden ville feilet.
For å forhindre dette, tilbyr `unittest.mock` `spec` og `autospec` argumentene.
- `spec=SomeClass`: Dette konfigurerer mocken til å ha samme API som `SomeClass`. Hvis du prøver å få tilgang til en metode eller et attributt som ikke eksisterer på den ekte klassen, vil en `AttributeError` bli kastet.
- `autospec=True` (eller `autospec=SomeClass`): Dette er enda kraftigere. Det fungerer som `spec`, men det sjekker også anropssignaturen til eventuelle mockede metoder. Hvis du kaller en metode med feil antall eller navn på argumenter, vil det kaste en `TypeError`, akkurat som det ekte objektet ville gjort.
from unittest.mock import create_autospec
# Opprett en mock som har samme grensesnitt som Notifier-klassen vår
spec_notifier = create_autospec(Notifier)
try:
# Dette vil feile umiddelbart på grunn av skrivefeilen
spec_notifier.send_mesage("hei")
except AttributeError as e:
print(e) # Utdata: Mock object has no attribute 'send_mesage'
try:
# Dette vil feile fordi signaturen er feil (ingen 'text'-nøkkel)
spec_notifier.send_message("hei")
except TypeError as e:
print(e) # Utdata: missing a required argument: 'text'
# Dette er den korrekte måten å kalle den på
spec_notifier.send_message(text="hei") # Dette fungerer!
spec_notifier.send_message.assert_called_once_with(text="hei")
Beste praksis: Bruk alltid `autospec=True` når du patche. Det gjør testene dine mer robuste ved å sikre at mockens grensesnitt samsvarer med det ekte objektets grensesnitt. Dette bidrar til å fange opp problemer forårsaket av refaktorering. `@patch('path.to.thing', autospec=True)`.
Reelt eksempel: Testing av en databehandlingstjeneste
La oss samle alt med et mer komplett eksempel. Vi har en `ReportGenerator` som avhenger av en database og et filsystem.
# i fil: app/services.py
class DatabaseConnector:
def get_sales_data(self, start_date, end_date):
# I virkeligheten ville dette spørre en database
raise NotImplementedError("Dette bør ikke kalles i tester")
class FileSaver:
def save_report(self, path, content):
# I virkeligheten ville dette skrive til en fil
raise NotImplementedError("Dette bør ikke kalles i tester")
# i fil: app/reports.py
from .services import DatabaseConnector, FileSaver
class ReportGenerator:
def __init__(self):
self.db_connector = DatabaseConnector()
self.file_saver = FileSaver()
def generate_sales_report(self, start_date, end_date, output_path):
"""Henter salgsdata og lagrer en formatert rapport."""
raw_data = self.db_connector.get_sales_data(start_date, end_date)
if not raw_data:
report_content = "Ingen salgsdata for denne perioden."
else:
total_sales = sum(item['amount'] for item in raw_data)
report_content = f"Totalt salg fra {start_date} til {end_date}: ${total_sales:.2f}"
self.file_saver.save_report(path=output_path, content=report_content)
return True
Nå skal vi skrive en enhetstest for `ReportGenerator.generate_sales_report` som mocker dens avhengigheter.
# i fil: tests/test_reports.py
import unittest
from datetime import date
from unittest.mock import patch, Mock
from app.reports import ReportGenerator
class TestReportGenerator(unittest.TestCase):
@patch('app.reports.FileSaver', autospec=True)
@patch('app.reports.DatabaseConnector', autospec=True)
def test_generate_sales_report_with_data(self, mock_db_connector_class, mock_file_saver_class):
"""Test rapportgenerering når databasen returnerer data."""
# Arrange: Sett opp mockene våre
mock_db_instance = mock_db_connector_class.return_value
mock_file_saver_instance = mock_file_saver_class.return_value
# Konfigurer database-mocken til å returnere noen falske data (Stub)
fake_data = [
{'id': 1, 'amount': 100.50},
{'id': 2, 'amount': 75.00},
{'id': 3, 'amount': 25.25}
]
mock_db_instance.get_sales_data.return_value = fake_data
start = date(2023, 1, 1)
end = date(2023, 1, 31)
path = '/reports/sales_jan_2023.txt'
# Act: Opprett en instans av klassen vår og kall metoden
generator = ReportGenerator()
result = generator.generate_sales_report(start, end, path)
# Assert: Verifiser interaksjonene og resultatene
# 1. Ble databasen kalt korrekt?
mock_db_instance.get_sales_data.assert_called_once_with(start, end)
# 2. Ble filspareren kalt med det korrekte, beregnede innholdet?
expected_content = "Totalt salg fra 2023-01-01 til 2023-01-31: $200.75"
mock_file_saver_instance.save_report.assert_called_once_with(
path=path,
content=expected_content
)
# 3. Returnerte metoden vår den korrekte verdien?
self.assertTrue(result)
Denne testen isolerer logikken innenfor `generate_sales_report` perfekt fra kompleksiteten i databasen og filsystemet, samtidig som den verifiserer at den interagerer korrekt med dem.
Beste praksis for effektiv mocking
- Hold Mocks enkle: En test som krever en veldig kompleks mock-konfigurasjon er ofte et tegn (en "test-lukt") på at enheten som testes er for kompleks og kan bryte med prinsippet om enkeltansvar (Single Responsibility Principle). Vurder å refaktorere produksjonskoden.
- Mock samarbeidspartnere, ikke alt: Du bør bare mocke objekter som enheten din under test kommuniserer med (dens samarbeidspartnere). Ikke mock objektet du tester selv.
- Foretrekk `autospec=True`: Som nevnt, dette gjør testene dine mer robuste ved å sikre at mockens grensesnitt samsvarer med det ekte objektets grensesnitt. Dette bidrar til å fange opp problemer forårsaket av refaktorering.
- Én Mock per Test (ideelt sett): En god enhetstest fokuserer på en enkelt atferd eller interaksjon. Hvis du finner deg selv å mocke mange forskjellige objekter i én test, kan det være bedre å dele den opp i flere, mer fokuserte tester.
- Vær spesifikk i dine påstander: Ikke bare sjekk `mock.method.assert_called()`. Bruk `assert_called_with(...)` for å sikre at interaksjonen skjedde med de riktige dataene. Dette gjør testene dine mer verdifulle.
- Testene dine er dokumentasjon: Bruk klare og beskrivende navn for testene dine og mock-objektene (f.eks. `mock_api_client`, `test_login_fails_on_network_error`). Dette gjør formålet med testen tydelig for andre utviklere.
Konklusjon
Test-doubles er ikke bare et verktøy for testing; de er en fundamental del av design av testbar, modulær og vedlikeholdbar programvare. Ved å erstatte ekte avhengigheter med kontrollerte erstatninger, kan du lage en testpakke som er rask, pålitelig og i stand til å verifisere alle hjørner av applikasjonens logikk.
Pythons unittest.mock-bibliotek tilbyr en verktøykasse i verdensklasse for å implementere disse mønstrene. Ved å mestre MagicMock, `patch`, og sikkerheten til `autospec`, låser du opp muligheten til å skrive virkelig isolerte enhetstester. Dette gir deg mulighet til å bygge komplekse applikasjoner med tillit, vel vitende om at du har et sikkerhetsnett av presise, målrettede tester for å fange regresjoner og validere nye funksjoner. Så gå videre, begynn å patche, og bygg mer robuste Python-applikasjoner i dag.